-- Debug
-- Create by weism
-- 调试的一些接口

-- 将table信息打印出来
function printTable(lua_table, desc)
    if DEBUG_MODE ~= 1 then
        return;
    end

    if desc == nil or desc == "" then
        error("printTable的desc参数不能为空")
    end

    local function printFunc(str)
        cclog(tostring(str))
    end

    printFunc("printTable, desc["..desc.."]:");

    local function doPrintTable(lua_table, indent, maxDepth, curDepth)
        if lua_table == nil or type(lua_table) ~= "table" then
            return
        end

        if maxDepth == nil then
            maxDepth = 20;
        end

        if maxDepth ~= nil and curDepth ~= nil and curDepth > maxDepth then
            return
        end

        indent = indent or 0
        curDepth = curDepth or 0
        printFunc(string.rep("    ", indent).."{")
        for k, v in pairs(lua_table) do
            if type(k) == "string" then
                k = string.format("%q", k)
            end
            local szSuffix = ""
            if type(v) == "table" then
                szSuffix = "{"
            end
            local szPrefix = string.rep("    ", indent+1)
            local formatting = szPrefix.."["..k.."]".." = "..szSuffix
            if type(v) == "table" then
                printFunc(formatting)
                doPrintTable(v, indent + 2, maxDepth, curDepth + 1)
                printFunc(szPrefix.."},")
            else
                local szValue = ""
                if type(v) == "string" then
                    szValue = string.format("%q", v)
                else
                    szValue = tostring(v)
                end
                printFunc(formatting..szValue..",")
            end
        end

        printFunc(string.rep("    ", indent).."}")
    end

    doPrintTable(lua_table);
end

-- 把table转成string
function tableToString(t)
    local ret = "";

    for k, v in pairs(t) do
        local key;
        local value;

        if type(k) == "number" then
            key = "[" .. k .. "]";
        elseif type(k) ~= "table" then
            key = tostring(k);
        else
            key = tableToString(k);
        end

        if type(v) == "table" then
            value = tableToString(v);

            -- ret = ret .. key .. "=" .. value .. ", ";
        else
            value = tostring(v);
            -- ret = ret .. key .. "=\"" .. value .. "\", ";
        end

        if type(v) == "string" then
            ret = ret .. key .. "=\"" .. value .. "\", ";
        else
            ret = ret .. key .. "=" .. value .. ", ";
        end
    end

    return string.format("{%s}", ret);
end

-- 取得指定函数的upvalue
function get_func_upvalue(func)
    local tbl = {};
    local n = 1;
    while true do
        local name, value = debug.getupvalue(func,n);
        if not name then
            break;
        end

        if value == nil then
            value = {};
        end

        tbl[name] = value;
        n= n + 1;
    end
    return tbl;
end

-- 查看变量
function watch(s, prefix, filter)
    local result = "";
    local sign = true;

    if not prefix then
        prefix = "";
    end

    local valueType = type(s);
    local realType = tolua and tolua.type(s) or valueType;
    if s == nil then
        result = "<nil>";
    elseif (valueType == "table" or (valueType == "userdata" and realType ~= "userdata")) and (type(s.get_ob_id) == "function" or
            s.__cname) then
        -- 对象，不希望打印出所有的 table 信息
        local ob_id = s.__cname;
        if type(s.get_ob_id) == "function" then
            ob_id = s:get_ob_id();
        end
        if type(ob_id) == "string" then
            result = string.format("object(%s)", ob_id);
        else
            result = string.format("object(unknown)");
        end

    elseif (valueType == "table") then
        local size = sizeof(s);
        if string.len(prefix) > 20 then
            result = "<table overflow>";
        else
            local str_list = { string.format("<table>   size : %d\r\n%s{\r\n", size, prefix) };

            local times = 1;
            for i, v in pairs(s) do
                sign = true;
                if (type(i) == "string") and (type(v) == "table") then
                    -- 如果key值是以下划线开头，隐藏table的内容
                    -- 这个处理为避免上下级互相引用时出现死循环
                    local key = i;

                    if (string.len(key) > 0) and (string.sub(key, 1, 1) == '_') then
                        table.insert(str_list, string.format("%s\t%s: <table hide>,\r\n",
                                     prefix, watch(i, prefix .. "\t", filter)));
                        sign = false;
                    end
                end

                if sign then
                    table.insert(str_list, string.format("%s\t%s:%s,\r\n", prefix,
                                 watch(i, prefix .. "\t", filter), watch(v, prefix .. "\t", filter)));
                end

                times = times + 1;
                if (filter and times > 20) or times > 500 then
                    table.insert(str_list, "... ...");
                    break;
                end

            end
            table.insert(str_list, string.format("%s}", prefix));
            result = table.concat(str_list, "");
        end
    elseif (valueType == "function") then
        result = "<function>";
    elseif (valueType == "string") then
        result = string.format("\"%s\"", string.gsub(s,'"', '\\"' ));
    elseif (valueType == "number") then
        result = tostring(s);
    else
        result = string.format("<%s %s>", tostring(s), tolua and tolua.type(s) or type(s));
    end

    return result;
end

-- 重载 debug.traceback
function custom_traceback()
    local result = { "stack traceback:\n", };

    local i = 3;
    local j = 1;
    local file, line, func, var, value;

    -- 遍历所有调用层次
    local source_info;
    local debug_get_info = debug.getinfo;
    local debug_get_local = debug.getlocal;
    repeat
        source_info = debug_get_info(i,'Sln'); -- .source;
        if not source_info then
            do break end
        end

        -- 取得文件名、行号、函数名信息
        file = source_info.short_src;
        line = source_info.currentline;
        func = source_info.name;

        if not file then
            file = "";
        end
        if not line then
            line = "";
        end
        if not func then
            func = "";
        end

        table.insert(result, string.format("\t(%d)%s:%s: in function '%s'\n",
                                           i - 2, file, line, func));
        if source_info.what ~= "C" and
           func ~= "_create" and func ~= "_destruct" and func ~= "new" then
            -- 遍历该层次的所有 local 变量
            j = 1;
            repeat
                var, value = debug_get_local(i, j);

                if var and not string.find(var, "%b()") then
                    if value then
                        table.insert(result, string.format("\t\t%s : %s\n", tostring(var),
                                                           watch(value, "\t\t", true)));
                    else
                        table.insert(result, string.format("\t\t%s : <nil>\n", tostring(var)));
                    end
                end

                j = j + 1;
            until not var;
        end

        i = i + 1;
    until not source_info;

    local str = table.concat(result, "");

    return str;
end
debugTraceback = debug.traceback;
debug.traceback = custom_traceback;

local statistics_info = {};

-- 用于检测 Lua 脚本中死循环、调用栈溢出等情况的钩子函数
-- 注意：本方法仅适用于 count hook 模式(即每 N 条指令执行一次 hook)
function lua_hook_count(arg)
    -- 调用栈深度超过500，判定为栈溢出，抛出异常
    if debug.getinfo(500) then
        error("Lua hook: detect stack overflow!");
    end

    local info = debug.getinfo(2, 'Slnf');
    if info == nil then
        return;
    end

    -- 累加次数
    -- 注意：这里的次数并非指函数被调用的次数，而是每隔 N 条指令后，仍然处于某个函数体内的次数
    if statistics_info[info.func] then
        statistics_info[info.func].count = statistics_info[info.func].count + 1;
    else
        statistics_info[info.func] = { time = os.time(), count = 0 }; 
    end

    if arg ~= "count" then 
        statistics_info[info.func] = { time = os.time(), count = 0 }; 
        return;
    end

    -- 距首次统计的时间差超过了30s，则将统计次数清零，避免正常的调用被误判
    if os.time() - statistics_info[info.func].time > 30 then
        statistics_info[info.func] = { time = os.time(), count = 0 }; 
    end 

    -- 统计的次数超过了设定的阈值，则判定发生了死循环，抛出异常
    if statistics_info[info.func].count > 10000 then 
        statistics_info[info.func] = { time = os.time(), count = 0 }; 
        error("Lua hook: detect endless loop!");
    end
end

-- 开启/关闭 lua hook
function enable_lua_hook(enabled)
    if enabled then
        -- 这里采取 count 模式，每3000条指令执行一次 hook
        debug.sethook(lua_hook_count, "", 3000);
    else
        -- 不传递参数表示关闭
        debug.sethook();
    end
end
